﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Snowdreamist.ComponentModel;

namespace Snowdreamist.Collection
{
    public class ThreadSafeDictionary<TKey, TValue> : ReadWriteCriticalRegion, IDictionary<TKey, TValue>
    {
        public ThreadSafeDictionary() : this(new Dictionary<TKey, TValue>()) { }

        public ThreadSafeDictionary(IEqualityComparer<TKey> comparer) : this(new Dictionary<TKey, TValue>(comparer)) { }

        public ThreadSafeDictionary(Dictionary<TKey, TValue> dictionary)
        {
            if (dictionary == null)
                throw new ArgumentNullException("dictionary");
            this._dict = dictionary;
        }

        public virtual void Add(TKey key, TValue value)
        {
            using (this.EnterUpgradeableReadRegion())
            {
                if (!this._dict.ContainsKey(key))
                {
                    using (this.EnterWriteRegion())
                    {
                        this._dict.Add(key, value);
                    }
                }
                else
                {
                    throw new ArgumentException("Key exists");
                }
            }
        }

        public virtual bool TryAdd(TKey key, TValue value)
        {
            using (this.EnterUpgradeableReadRegion())
            {
                if (!this._dict.ContainsKey(key))
                {
                    using (this.EnterWriteRegion())
                    {
                        this._dict.Add(key, value);
                        return true;
                    }
                }
                else
                {
                    return false;
                }
            }
        }

        public virtual bool TryAdd(TKey key, Func<TValue> valueFunc)
        {
            if (valueFunc == null)
                throw new ArgumentNullException("valueFunc");

            using (this.EnterUpgradeableReadRegion())
            {
                if (!this._dict.ContainsKey(key))
                {
                    using (this.EnterWriteRegion())
                    {
                        this._dict.Add(key, valueFunc());
                        return true;
                    }
                }
                else
                {
                    return false;
                }
            }
        }

        public virtual TValue GetOrAdd(TKey key, Func<TValue> valueFunc)
        {
            if (valueFunc == null)
                throw new ArgumentNullException("valueFunc");

            TValue value = default(TValue);
            this.GetOrAdd(key, valueFunc, out value);

            return value;
        }

        /// <summary>
        /// Get or add
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="valueFunc">the function to get value</param>
        /// <param name="value">value</param>
        /// <returns>True for add</returns>
        protected virtual bool GetOrAdd(TKey key, Func<TValue> valueFunc, out TValue value)
        {
            using (this.EnterUpgradeableReadRegion())
            {
                if (this._dict.ContainsKey(key))
                {
                    value = this._dict[key];
                    return false;
                }
                else
                {
                    using (this.EnterWriteRegion())
                    {
                        if (this._dict.ContainsKey(key))
                        {
                            value = this._dict[key];
                            return false;
                        }
                        else
                        {
                            value = valueFunc();
                            this._dict[key] = value;
                            return true;
                        }
                    }
                }
            }
        }

        public virtual bool ContainsKey(TKey key)
        {
            using (this.EnterReadRegion())
            {
                return this._dict.ContainsKey(key);
            }
        }

        public virtual ICollection<TKey> Keys
        {
            get
            {
                return new Collection<TKey>(this._dict.Keys, this);
            }
        }

        public virtual bool Remove(TKey key)
        {
            using (this.EnterWriteRegion())
            {
                return this._dict.Remove(key);
            }
        }

        public virtual bool TryGetValue(TKey key, out TValue value)
        {
            using (this.EnterReadRegion())
            {
                return this._dict.TryGetValue(key, out value);
            }
        }

        public virtual ICollection<TValue> Values
        {
            get
            {
                return new Collection<TValue>(this._dict.Values, this);
            }
        }

        public virtual TValue this[TKey key]
        {
            get
            {
                using (this.EnterReadRegion())
                {
                    if (this._dict.ContainsKey(key))
                    {
                        return this._dict[key];
                    }
                    else
                    {
                        throw new KeyNotFoundException();
                    }
                }
            }
            set
            {
                using (this.EnterWriteRegion())
                {
                    this[key] = value;
                }
            }
        }

        public virtual void Add(KeyValuePair<TKey, TValue> item)
        {
            this.Add(item.Key, item.Value);
        }

        public virtual void Clear()
        {
            using (this.EnterWriteRegion())
            {
                this._dict.Clear();
            }
        }

        public virtual bool Contains(KeyValuePair<TKey, TValue> item)
        {
            using (this.EnterReadRegion())
            {
                if (this._dict.ContainsKey(item.Key))
                {
                    var value = this._dict[item.Key];
                    if (item.Value.Equals(value))
                        return true;
                }
                return false;
            }
        }

        public virtual void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            using (this.EnterReadRegion())
            {
                int i = arrayIndex;
                foreach (var pair in this._dict)
                    array[i++] = pair;
            }
        }

        public virtual int Count
        {
            get
            {
                return this._dict.Count;
            }
        }

        public virtual bool IsReadOnly
        {
            get
            {
                return false;
            }
        }

        public virtual bool Remove(KeyValuePair<TKey, TValue> item)
        {
            return this.Remove(item.Key);
        }

        public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            var region = this.EnterReadRegion();

            try
            {
                foreach (var pair in this._dict)
                    yield return pair;
            }
            finally
            {
                // break in for
                region.Dispose();
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #region private

        private Dictionary<TKey, TValue> _dict;

        private class Collection<T> : ICollection<T>
        {
            public Collection(ICollection<T> values, ReadWriteCriticalRegion region)
            {
            }

            public void Add(T item)
            {
                throw new NotImplementedException();
            }

            public void Clear()
            {
                throw new NotImplementedException();
            }

            public bool Contains(T item)
            {
                throw new NotImplementedException();
            }

            public void CopyTo(T[] array, int arrayIndex)
            {
                throw new NotImplementedException();
            }

            public int Count
            {
                get { throw new NotImplementedException(); }
            }

            public bool IsReadOnly
            {
                get { throw new NotImplementedException(); }
            }

            public bool Remove(T item)
            {
                throw new NotImplementedException();
            }

            public IEnumerator<T> GetEnumerator()
            {
                throw new NotImplementedException();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                throw new NotImplementedException();
            }
        }

        #endregion private
    }
}